#pragma unmanaged

namespace UnManaged
{
#	include <System.hpp>
#	include <Events.hpp>
#	include <Module.hpp>

#	include <IDK.hpp>

#	include <Camera.hpp>
#	include <ThirdPersonCamera.hpp>

	const float ThirdPersonCamera::DEFAULT_SPRING_CONSTANT = 32.0f;
	const float ThirdPersonCamera::DEFAULT_DAMPING_CONSTANT = 8.0f;

	const Math::Vector3 ThirdPersonCamera::WORLD_XAXIS(1.0f, 0.0f, 0.0f);
	const Math::Vector3 ThirdPersonCamera::WORLD_YAXIS(0.0f, 1.0f, 0.0f);
	const Math::Vector3 ThirdPersonCamera::WORLD_ZAXIS(0.0f, 0.0f, 1.0f);

	ThirdPersonCamera::ThirdPersonCamera() : TBasicCamera()
	{
		m_springConstant = DEFAULT_SPRING_CONSTANT;
		m_dampingConstant = DEFAULT_DAMPING_CONSTANT;

		m_offsetDistance = 0.0f;
		m_headingDegrees = 0.0f;
		m_pitchDegrees = 0.0f;

		m_targetYAxis = Math::Vector3(0.0f, 1.0f, 0.0f);

		m_xAxis = Math::Vector3(1.0f, 0.0f, 0.0f);
		m_yAxis = Math::Vector3(0.0f, 1.0f, 0.0f);
		m_zAxis = Math::Vector3(0.0f, 0.0f, 1.0f);
		m_viewDir = Math::Vector3(0.0f, 0.0f, -1.0f);
	}

	ThirdPersonCamera::~ThirdPersonCamera()
	{
	}

	void ThirdPersonCamera::LookAt(const Math::Vector3 &target)
	{
		m_target = target;
	}

	void ThirdPersonCamera::LookAt(const Math::Vector3 &eye, const Math::Vector3 &target, const Math::Vector3 &up)
	{
		//mMatrixView = Math::LookAt(eye, target, up);
		
		m_eye = eye;
		m_target = target;
		m_targetYAxis = up;

		m_zAxis = eye - target;
		m_zAxis = Math::Normalize(m_zAxis);

		m_viewDir = -m_zAxis;

		m_xAxis = Math::Cross(up, m_zAxis);
		m_xAxis = Math::Normalize(m_xAxis);

		m_yAxis = Math::Cross(m_zAxis, m_xAxis);
		m_yAxis = Math::Normalize(m_yAxis);
		m_xAxis = Math::Normalize(m_xAxis);

		mMatrixView[0][0] = m_xAxis.x;
		mMatrixView[1][0] = m_xAxis.y;
		mMatrixView[2][0] = m_xAxis.z;
		mMatrixView[3][0] = -Math::Dot(m_xAxis, eye);

		mMatrixView[0][1] = m_yAxis.x;
		mMatrixView[1][1] = m_yAxis.y;
		mMatrixView[2][1] = m_yAxis.z;
		mMatrixView[3][1] = -Math::Dot(m_yAxis, eye);

		mMatrixView[0][2] = m_zAxis.x;
		mMatrixView[1][2] = m_zAxis.y;
		mMatrixView[2][2] = m_zAxis.z;    
		mMatrixView[3][2] = -Math::Dot(m_zAxis, eye);

		m_orientation = Math::FromMatrix(mMatrixView);

		Math::Vector3 offset = m_target - m_eye;

		m_offsetDistance = Math::Sqrt(Math::Sqr(offset));
	}

	void ThirdPersonCamera::Rotate(float headingDegrees, float pitchDegrees)
	{
		m_headingDegrees = -headingDegrees;
		m_pitchDegrees = -pitchDegrees;
	}

	void ThirdPersonCamera::Update(float elapsedTimeSec)
	{
		UpdateOrientation(elapsedTimeSec);
		UpdateViewMatrix(elapsedTimeSec);
		//Base::Update(elapsedTimeSec);
	}

	void ThirdPersonCamera::UpdateOrientation(float elapsedTimeSec)
	{
		m_pitchDegrees *= elapsedTimeSec;
		m_headingDegrees *= elapsedTimeSec;

		if (m_headingDegrees != 0.0f)
		{
			Math::Quaternion rot = Math::Rotate(m_targetYAxis, Math::Radian(Math::Degree(m_headingDegrees)));
			m_orientation = rot * m_orientation;
		}

		if (m_pitchDegrees != 0.0f)
		{
			Math::Quaternion rot = Math::Rotate(WORLD_XAXIS, Math::Radian(Math::Degree(m_pitchDegrees)));
			m_orientation = m_orientation * rot;
		}
	}


	void ThirdPersonCamera::CalculateMatrix(IDK::ICamera::MatrixMode::Enum mode, Math::Matrix44& dest)
	{
			switch(mode)
			{
			case MatrixMode::View :
				dest = mMatrixView;
				return;
			default:
				Base::CalculateMatrix(mode, dest);
			}
	}
	
	void ThirdPersonCamera::UpdateViewMatrix(float elapsedTimeSec)
	{
		mMatrixView = Math::ToMatrix(m_orientation);

		m_xAxis = Math::Vector3(mMatrixView[0][0], mMatrixView[1][0], mMatrixView[2][0]);
		m_yAxis = Math::Vector3(mMatrixView[0][1], mMatrixView[1][1], mMatrixView[2][1]);
		m_zAxis = Math::Vector3(mMatrixView[0][2], mMatrixView[1][2], mMatrixView[2][2]);

		// Calculate the new camera position. The 'idealPosition' is where the
		// camera should be position. The camera should be positioned directly
		// behind the target at the required offset distance. What we're doing here
		// is rather than have the camera immediately snap to the 'idealPosition'
		// we slowly move the camera towards the 'idealPosition' using a spring
		// system.
		//
		// References:
		//   Stone, Jonathan, "Third-Person Camera Navigation," Game Programming
		//     Gems 4, Andrew Kirmse, Editor, Charles River Media, Inc., 2004.

		Math::Vector3 idealPosition = m_target + m_zAxis * m_offsetDistance;
		Math::Vector3 displacement = m_eye - idealPosition;
		Math::Vector3 springAcceleration =
			(-m_springConstant * displacement) - (m_dampingConstant * m_velocity);

		m_velocity += springAcceleration * elapsedTimeSec;
		m_eye += m_velocity * elapsedTimeSec;

		// The view matrix is always relative to the camera's current position
		// 'm_eye'. Since a spring system is being used here 'm_eye' will be
		// relative to 'idealPosition'. When the camera is no longer being
		// moved 'm_eye' will become the same as 'idealPosition'. The local
		// x, y, and z axes that were extracted from the camera's orientation
		// 'm_orienation' is correct for the 'idealPosition' only. We need
		// to recompute these axes so that they're relative to 'm_eye'. Once
		// that's done we can use those axes to reconstruct the view matrix.

		m_zAxis = m_eye - m_target;
		m_zAxis = Math::Normalize(m_zAxis);

		m_xAxis = Math::Cross(m_targetYAxis, m_zAxis);
		m_xAxis = Math::Normalize(m_xAxis);

		m_yAxis = Math::Cross(m_zAxis, m_xAxis);
		m_yAxis = Math::Normalize(m_yAxis);

		mMatrixView = Math::Matrix44();

		mMatrixView[0][0] = m_xAxis.x;
		mMatrixView[1][0] = m_xAxis.y;
		mMatrixView[2][0] = m_xAxis.z;
		mMatrixView[3][0] = -Math::Dot(m_xAxis, m_eye);

		mMatrixView[0][1] = m_yAxis.x;
		mMatrixView[1][1] = m_yAxis.y;
		mMatrixView[2][1] = m_yAxis.z;
		mMatrixView[3][1] = -Math::Dot(m_yAxis, m_eye);

		mMatrixView[0][2] = m_zAxis.x;
		mMatrixView[1][2] = m_zAxis.y;
		mMatrixView[2][2] = m_zAxis.z;   
		mMatrixView[3][2] = -Math::Dot(m_zAxis, m_eye);

		m_viewDir = -m_zAxis;

		mChangeMatrix.Include(MatrixMode::View);
	}
}